package test.tftp;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

import javax.swing.JTextArea;


public class ClientThread extends Thread
{
    // tftp数据包类型定义 (opcode)
    final static int RRQ = 1;

    final static int WRQ = 2;

    final static int DAT = 3;

    final static int ACK = 4;

    final static int ERR = 5;

    private byte[] bytes = new byte[516]; // 输出用

    private byte[] bytesin = new byte[516]; // 输入用

    private byte[] data; // 用于提取信息

    private DatagramSocket dskt;

    private short block = 0; // 目标块号

    private short blockin;

    private FileInputStream in; // 便于对512个字节一起操作

    private FileOutputStream out;

    private boolean EOF = false;

    private int lastOpcode;

    private short lastBlock;

    private DatagramPacket lastPack;

    private int retry = 8;

    private InetAddress hostAddr;

    private int port;

    private int opcode;

    private byte[] fname;

    private byte[] mode;

    private String path;

    private String fileDir;

    private JTextArea texta;

    private Para param;

    private Random rd;

    private DataInputStream din; // 用于一次读取一个short

    public ClientThread(InetAddress hostAddr, int port, int opcode, byte[] fname, byte[] mode, String log_path,
            String f_path, JTextArea area, Para param)
    {
        this.hostAddr = hostAddr;
        this.port = port;
        this.opcode = opcode;
        this.fname = fname;
        this.mode = mode;
        this.path = log_path;
        this.fileDir = f_path;
        this.texta = area;
        this.param = param;
        rd = new Random();
    }

    public void run()
    {
        try
        {
            dskt = new DatagramSocket(); // 系统会分配给他一个空闲的端口
        }
        catch (SocketException ex)
        { // 没有可用端口
            return;
        }
        if (opcode == WRQ)
        {
            if (!testName(new String(fname)))
            {
                sendErrPacket(6, "The file has a same name!");
                return;
            }
            try
            {
                log_start();
                wrq();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
        else if (opcode == RRQ)
        {
            if (testName(new String(fname)))
            {
                sendErrPacket(1, "No such a file!");
                return;
            }
            try
            {
                log_start();
                rrq();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }

    private void wrq() throws IOException
    {
        try
        {
            out = new FileOutputStream(fileDir + new String(fname));
        }
        catch (FileNotFoundException ex)
        {
        }
        sendAck((short) 0);
        block = 1;
        lastOpcode = ACK;
        lastBlock = 0;
        do
        {
            DatagramPacket pack = new DatagramPacket(bytesin, 516);
            boolean status = false;
            retry = 8;
            do
            {
                try
                {
                    dskt.setSoTimeout(1000); // 设置计时器为 1 秒
                }
                catch (SocketException ex1)
                {
                }
                try
                {
                    dskt.receive(pack);
                }
                catch (java.net.SocketTimeoutException ex2)
                { // 超时处理
                    if (retry < 0)
                    {
                        streamClose(out);
                        log_end();
                        return;
                    }
                    reSend();
                    retry--;
                }
                catch (IOException ex3)
                {
                    streamClose(out);
                    log_end();
                    return;
                }
                // 希望收到 DAT、block
                data = pack.getData();
                din = new DataInputStream(new ByteArrayInputStream(data));
                int code = din.readShort();
                blockin = din.readShort();
                if (rd.nextInt(100) >= 2 && code == DAT)
                { // 加噪音
                    if (blockin - block > 0)
                    { // something may be wrong
                        streamClose(out);
                        log_end();
                        return;
                    }
                    if (blockin - block < 0)
                    { // 收到重复的块，重发ACK
                        sendAck(blockin);
                        continue;
                    }
                    status = true; // blockin = block
                }
            } while (!status);
            // 收到了想要的数据包
            try
            {
                out.write(data, 4, data.length - 4);
            }
            catch (IOException ex)
            {
            }
            if (data.length < 516) // 文件传输完毕
                EOF = true;
            sendAck(block);
            lastOpcode = ACK;
            lastBlock = block;
            System.out.println(block + "**" + (block + 1)); // 用于打印 调试
            if (param.getDetail())
                texta.append("\n" + hostAddr.getHostAddress() + "-------->" + block);
            block++;
        } while (!EOF);
        log_end();
        streamClose(out);
    }

    private void rrq() throws IOException
    {
        try
        {
            in = new FileInputStream(fileDir + new String(fname));
        }
        catch (FileNotFoundException ex)
        {
        }
        block = 1;
        do
        {
            // 先发送一个数据包
            int len = 4;
            bytes[0] = (byte) ((DAT >> 8) & 0xFF);
            bytes[1] = (byte) (DAT & 0xFF);
            bytes[2] = (byte) ((block >> 8) & 0xFF);
            bytes[3] = (byte) (block & 0xFF);
            byte[] buf = new byte[512];
            int n = 0; // 本次读取文件中的字节数
            try
            {
                n = in.read(buf, 0, 512);
            }
            catch (IOException ex)
            {
                n = 0; // 使EOF为true
            }
            if (n == -1) // 文件结束返回 -1
                n = 0;
            System.arraycopy(buf, 0, bytes, 4, n);
            len += n;
            if (n != 512)
            {
                EOF = true;
            }
            DatagramPacket pack = new DatagramPacket(bytes, len, hostAddr, port);
            try
            {
                if (rd.nextInt(100) >= 2)
                    dskt.send(pack);
                System.out.println(block + "**" + (block + 1)); // 打印调试
                if (param.getDetail())
                    texta.append("\n" + hostAddr.getHostAddress() + "<--------" + block);
                lastOpcode = DAT;
                lastPack = new DatagramPacket(bytes, len, hostAddr, port);
            }
            catch (IOException ex1)
            {
                streamClose(in);
                return;
            }
            DatagramPacket pack1 = new DatagramPacket(bytesin, 516); // 用于接收ACK
            boolean status = false;
            retry = 8;
            do
            {
                try
                {
                    dskt.setSoTimeout(1000); // 设置计时器为 1 秒
                }
                catch (SocketException ex1)
                {
                }
                try
                {
                    dskt.receive(pack1);
                }
                catch (java.net.SocketTimeoutException ex2)
                { // 超时处理
                    if (retry < 0)
                    {
                        streamClose(in);
                        return;
                    }
                    reSend();
                    retry--;
                }
                catch (IOException ex3)
                {
                    streamClose(in);
                    return;
                }
                // 希望收到 ACK
                data = pack1.getData();
                din = new DataInputStream(new ByteArrayInputStream(data));
                int code = din.readShort();
                blockin = din.readShort();
                if (rd.nextInt(100) >= 2 && code == ACK)
                { // 对收到的数据加噪音
                    if (blockin - block > 0)
                    {// something may be wrong
                        streamClose(in);
                        return;
                    }
                    if (blockin - block < 0)
                    { // 收到重复的块，重发ACK
                        reSend();
                        continue;
                    }
                    block++;
                    status = true;
                }
            } while (!status);
        } while (!EOF);
        log_end();
    }

    protected void sendErrPacket(int errcode, String msg)
    {
        bytes[0] = (byte) ((ERR >> 8) & 0xFF);
        bytes[1] = (byte) (ERR & 0xFF);
        bytes[2] = (byte) ((errcode >> 8) & 0xFF);
        bytes[3] = (byte) (errcode & 0xFF);
        System.arraycopy(msg.getBytes(), 0, bytes, 4, (msg.getBytes()).length);
        bytes[4 + msg.length()] = 0;
        DatagramPacket pack = new DatagramPacket(bytes, 5 + msg.length(), hostAddr, port);
        try
        {
            dskt.send(pack);
        }
        catch (IOException ex)
        {
        }
    }

    // 检查是否有重名文件，无，返回true 有，返回false
    private boolean testName(String fname)
    {
        File directory = new File(fileDir);
        String[] fns = directory.list();
        for (int i = 0; i < fns.length; i++)
            if (fns[i].equals(fname))
                return false;
        return true;
    }

    private void sendAck(short block)
    {
        bytes[0] = (byte) ((ACK >> 8) & 0xFF);
        bytes[1] = (byte) (ACK & 0xFF);
        bytes[2] = (byte) ((block >> 8) & 0xFF);
        bytes[3] = (byte) (block & 0xFF);
        DatagramPacket dpkt = new DatagramPacket(bytes, 4, hostAddr, port);
        try
        {
            if (rd.nextInt(100) >= 2)
                dskt.send(dpkt);
        }
        catch (IOException ex)
        {
        }
    }

    private void reSend()
    {
        if (lastOpcode == ACK)
        {
            if (rd.nextInt(100) >= 2)
                sendAck(lastBlock);
        }
        else if (lastOpcode == DAT)
        {
            try
            {
                if (rd.nextInt(100) >= 2)
                    dskt.send(lastPack);
            }
            catch (IOException ex)
            {
            }
        }
    }

    private void streamClose(FileInputStream s)
    {
        try
        {
            s.close();
        }
        catch (IOException ex)
        {
        }
    }

    private void streamClose(FileOutputStream s)
    {
        try
        {
            s.close();
        }
        catch (IOException ex)
        {
        }
    }

    private void log_start()
    {
        try
        {
            FileOutputStream logfile = new FileOutputStream(path, true);
            Date date = new Date();
            DateFormat d = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String str = d.format(date);
            if (opcode == WRQ)
            {
                logfile.write(("\r\n" + hostAddr.toString() + " 在 " + str + " 以 " + new String(mode) + " 模式 开始上传文件: " + new String(
                        fname)).getBytes());
                texta.append(("\n" + hostAddr.toString() + " 在 " + str + " 以 " + new String(mode) + " 开始上传文件: " + new String(
                        fname)));
            }
            else if (opcode == RRQ)
            {
                logfile.write(("\r\n" + hostAddr.toString() + " 在 " + str + " 以 " + new String(mode) + " 模式 开始下载文件: " + new String(
                        fname)).getBytes());
                texta.append(("\n" + hostAddr.toString() + " 在 " + str + " 以 " + new String(mode) + " 开始下载文件: " + new String(
                        fname)));
            }
            logfile.close();
        }
        catch (IOException ex)
        {
        }
    }

    private void log_end()
    {
        try
        {
            FileOutputStream logfile = new FileOutputStream(path, true);
            Date date = new Date();
            DateFormat d = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String str = d.format(date);
            if (opcode == WRQ)
            {
                logfile.write(("\r\n" + hostAddr.toString() + " 在 " + str + " 以 " + new String(mode) + " 结束上传文件: " + new String(
                        fname)).getBytes());
                texta.append(("\n" + hostAddr.toString() + " 在 " + str + " 以 " + new String(mode) + " 结束上传文件: " + new String(
                        fname)));
            }
            else if (opcode == RRQ)
            {
                logfile.write(("\r\n" + hostAddr.toString() + " 在 " + str + " 以 " + new String(mode) + " 结束下载文件: " + new String(
                        fname)).getBytes());
                texta.append(("\n" + hostAddr.toString() + " 在 " + str + " 以 " + new String(mode) + " 结束下载文件: " + new String(
                        fname)));
            }
            logfile.close();
            System.out.println("HDHHD");
        }
        catch (IOException ex)
        {
        }
    }
}